package com.tvunetworks.device.extra.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.Maps;
import com.tvunetworks.center.common.constant.LocalConstant;
import com.tvunetworks.center.common.constant.ReceiverCommandConstant;
import com.tvunetworks.center.common.model.CommandParam;
import com.tvunetworks.center.common.model.ListDeviceParam;
import com.tvunetworks.center.common.model.dto.GridDto;
import com.tvunetworks.center.common.model.param.SourceAttrParam;
import com.tvunetworks.center.common.model.vo.FeatureInfoVo;
import com.tvunetworks.center.common.util.JsonUtil;
import com.tvunetworks.center.common.util.MyUtil;
import com.tvunetworks.center.common.util.Result;
import com.tvunetworks.center.common.util.ResultUtil;
import com.tvunetworks.center.gs.model.Device;
import com.tvunetworks.device.extra.constants.ResultConstant;
import com.tvunetworks.device.extra.controller.feign.DeviceExtraFeignController;
import com.tvunetworks.device.extra.exception.ShareException;
import com.tvunetworks.device.extra.model.*;
import com.tvunetworks.device.extra.model.param.GridDeviceParam;
import com.tvunetworks.device.extra.model.param.GridParam;
import com.tvunetworks.device.extra.service.GridService;
import com.tvunetworks.device.extra.service.feign.*;
import com.tvunetworks.device.extra.util.Beanutils;
import com.tvunetworks.device.extra.util.CommandUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author andy
 * @since 2022/3/23
 **/
@Service
@Slf4j
public class GridServiceImpl implements GridService {
    @Autowired
    private DeviceFeignService deviceFeignService;
    @Autowired
    private GcsManageFeignService gcsFeignInterface;
    @Autowired
    private DeviceExtraFeignController deviceExtraFeignService;
    @Autowired
    private CommandUtil commandUtil;
    @Autowired
    private MediaFeignService mediaFeignService;
    @Autowired
    private RwsFeignService rwsFeignService;
    @Autowired
    private ShareFeignService shareFeignService;
    @Autowired
    private NewTpcFeignService newTpcFeignService;
    @Autowired
    private UserFeignService userFeignService;
    /**
     * notify r to refresh gcs config
     */
    public static final String CATEGORY_ID_FOR_REFRESH_GCS_CONFIG = "2152868864";
    /**refresh gcs optionType*/
    public static final String OPERATE_TYPE_FOR_REFRESH_GCS_CONFIG = "600";
    /**pack code*/
    public static final String SUFFIX = "0000000000000001";
    /** pack */
    public static final Integer CLOUD_R_TYPE = 1;
    /**grid success code*/
    public static final String GRID_LIVE_SUCCESS_CODE="0";
    /**prefix*/
    public static final String PP_PREFIX="fffffffbb";
    /**
     * channel prefix
     */
    public static final String CHANNEL_PREFIX="fffffff11";
    /**
     * grid live retry count
     */
    @Value("${grid.live.retryCount:5}")
    private Integer retryCount;
    /**
     * sync live status time
     */
    @Value("${grid.live.syncTime:10000}")
    private Long syncTime;
    /**
     * live retry interval time
     */
    @Value("${grid.interval.time:2000}")
    private Long interValTime;

    @Override
    public Result start(GridParam gridParam) throws Exception {
        Result result = checkParamPass(gridParam);
        if(!result.assertSuccess()){
            return result;
        }
        if(gridParam.getResourceId()==null){
            gridParam.setResourceId(SUFFIX);
        }
        List<GridDto> grids = mediaFeignService.getGrids(gridParam.getRid());
        if(CollectionUtils.isEmpty(grids)||needPair(grids,gridParam)){
            Result<Object> pair = pair(gridParam);
            if (!pair.assertSuccess()) {
                return pair;
            }
        }
        addGridPermission(gridParam);
        Result<String> stringResult = retryLive(gridParam,retryCount);
        if(stringResult.assertSuccess()&&syncStatus(gridParam)){
            updateGird(gridParam,1);
        }else if (stringResult.assertSuccess()){
           return ResultConstant.GIRD.getError(ResultConstant.GIRD.ERROR_6);
        }
        return stringResult;
    }

    /**
     * sync status
     * @param gridParam gridParam
     */
    private boolean syncStatus(GridParam gridParam) {
        boolean flag=false;
        try {
            String pairPid = gridParam.getPairPid();
            long finalTime = System.currentTimeMillis() + syncTime;
            while (finalTime>System.currentTimeMillis()&&!flag){
                Thread.sleep(1000);
                Device currentDevice = deviceFeignService.getCurrentDevice(pairPid);
                if(LocalConstant.DeviceStatus.LIVING.equals(currentDevice.getStatus())){
                    flag=true;
                }
            }
        }catch (Exception e){
            log.error("syncStatus exception:",e);
        }

        return flag;
    }

    /**
     * check param
     * @param gridParam gridparam
     * @return r
     */
    private Result checkParamPass(GridParam gridParam) {
        Result tResult = ResultUtil.success();
        if(StringUtils.isEmpty(gridParam.getTargetId())||StringUtils.isEmpty(gridParam.getRid())||StringUtils.isEmpty(gridParam.getPairPid())){
            tResult= ResultUtil.paramError("targetId or rid or pairId is empty");
        }

        //living判断
        if (assertLiving(gridParam.getPairPid())) {
            tResult= ResultUtil.paramError("current r is living");
        }
        if(gridParam.getRid().equals(gridParam.getPairPid())){
            tResult =ResultUtil.paramError("pairId is error");
        }
        if (!gridParam.getPairPid().startsWith(PP_PREFIX)&&!gridParam.getPairPid().startsWith(CHANNEL_PREFIX)&&!hasGridFeature(gridParam)) {
            tResult =ResultConstant.GIRD.getError(ResultConstant.GIRD.ERROR_5);
        }
        //数量是否超限制
        if(gridParam.getLimit()!=null&&reachLimit(gridParam.getLimit(),gridParam.getRid())){
            tResult = ResultConstant.GIRD.getError(ResultConstant.GIRD.SHARE_LIMIT);
        }
        return tResult;
    }

    private void addGridPermission(GridParam gridParam) {
        String resourceId = StringUtils.isEmpty(gridParam.getResourceId())?SUFFIX:gridParam.getResourceId();
        String gridId = gridParam.getRid() +resourceId;
        log.error("add grid permission:{}",gridParam);
        Result<String> stringResult = userFeignService.setGridByUserId(gridParam.getUserId(), gridId);
        log.error("add grid permission result:{}",stringResult);
    }

    private boolean hasGridFeature(GridParam gridParam) {
        log.error("get feature:{}",gridParam);
        Result<List<FeatureInfoVo>> featureListByPeerId = newTpcFeignService.getFeatureListByPeerId(gridParam.getPairPid());
        log.error("grid feature result:{}",featureListByPeerId);
        if (featureListByPeerId.assertSuccess()) {
            return featureListByPeerId.getResult().stream().anyMatch(e -> e.getFeatureName().contains("GlobalGrid"));
        }
        return false;
    }

    private boolean reachLimit(Integer limit, String rid) {
        Result<List<LiveRecordDto>> liveRecordDtoResult = shareFeignService.currentRLiveList(rid);
        if(liveRecordDtoResult.assertSuccess()){
            return liveRecordDtoResult.getResult().size()>=limit;
        }
        return false;
    }

    private Result<String> retryLive(GridParam gridParam, int count) throws IOException, InterruptedException {
        CommandParam commandParam = buildCommand(gridParam,
                ReceiverCommandConstant.CategoryId.PACK_RECEIVER_CORE,
                ReceiverCommandConstant.OperationType.START_LIVE,gridParam.getPairPid().startsWith(PP_PREFIX)?"0":null);
        log.error("grid live param:{} ,retry count:{}",commandParam,count);
        Result<String> command = rwsFeignService.command(commandParam);
        if(!command.assertSuccess()){
            return command;
        }
        Map map = JsonUtil.toObject(command.getResult(), Map.class);
        log.error("gird live result:{} retry count:{}",command,count);
        count=count-1;
        //重试
        if(!GRID_LIVE_SUCCESS_CODE.equals(map.get("ErrorCode")+"")&&count>0){
            if(ResultConstant.GIRD.ERROR_2.getThirdCode().equals(map.get("ErrorCode")+"")){
                pair(gridParam);
            }
            Thread.sleep(interValTime);
            return retryLive(gridParam,count);
        }
        if(command.assertSuccess()&&! GRID_LIVE_SUCCESS_CODE.equals(map.get("ErrorCode")+"")){
            ResultConstant.GIRD.buildError(command,map.get("ErrorCode")+"");
        }
        return command;
    }

    private boolean assertLiving(String rid) {
        Device currentDevice = deviceFeignService.getCurrentDevice(rid);
        return LocalConstant.DeviceStatus.LIVING.equals(currentDevice.getStatus());
    }

    private boolean existAndAddGrid(GridParam gridParam) {
        GridDto gridDto=new GridDto();
        gridDto.setRid(gridParam.getRid());
        gridDto.setTargetId(gridParam.getTargetId());
        gridDto.setPairPid(gridParam.getPairPid());
        gridDto.setResourceId(gridParam.getResourceId());
        gridDto.setUserId(gridParam.getUserId());
        gridDto.setNickname(gridParam.getGridName());
        Result<Object> objectResult = addOrUpdateGrid(gridDto);
        return objectResult.assertSuccess();
    }

    /**
     * build command
     * @param gridParam grid
     * @return c
     */
    private CommandParam buildCommand(GridParam gridParam,String categoryId,String optionType,String liveMode) {
        CommandParam commandParam=new CommandParam();
        commandParam.setRid(gridParam.getPairPid());
        commandParam.setCategoryId(categoryId);
        commandParam.setOperationType(optionType);
        commandParam.setData(startStopLive(gridParam.getRid()+gridParam.getResourceId(),liveMode));
        commandParam.setOrigin(gridParam.getPairPid().startsWith(PP_PREFIX)?"pp":null);
        return commandParam;
    }

    /**
     * 判断是否需要配对
     * @param grids g
     * @param gridParam g
     * @return b
     */
    private boolean needPair(List<GridDto> grids, GridParam gridParam) {
        return grids.stream().anyMatch(e -> gridParam.getPairPid().equals(e.getPairPid())
                && gridParam.getResourceId().equals(e.getResourceId())
                && !Objects.equals(1,e.getStatus()));
    }

    private void updateGird(GridParam gridParam,int status) {
        GridDto gridDto = new GridDto();
        gridDto.setStatus(status);
        gridDto.setUserId(gridParam.getUserId());
        gridDto.setRid(gridParam.getRid());
        gridDto.setTargetId(gridParam.getTargetId());
        gridDto.setNickname(gridParam.getGridName());
        gridDto.setPairPid(gridParam.getPairPid());
        gridDto.setResourceId(gridParam.getResourceId());
        gridDto.setId(gridParam.getId());
        mediaFeignService.addOrUpdateGridOutput(gridDto);
        mediaFeignService.recordGridLive(gridDto,gridParam.getPlatform()==null? gridParam.getOrigin() : gridParam.getPlatform());
    }

    @Override
    public Result stop(GridParam gridParam)throws IOException {
        if(gridParam.getPairPid()==null){
            return ResultUtil.paramError("pairPid is empty");
        }
        updateSource(gridParam, false);
        CommandParam commandParam = buildCommand(gridParam, ReceiverCommandConstant.CategoryId.PACK_RECEIVER_CORE, ReceiverCommandConstant.OperationType.STOP_LIVE, null);
        log.error("grid stop live param:{}",commandParam);
        Result<String> command = rwsFeignService.command(commandParam);
        log.error("gird stop live result:{}",command);
        if (command.assertSuccess()){
            updateGird(gridParam,0);
        }
        return command;
    }


    /**
     * 更新source
     * @param gridParam rid
     * @param flag flag
     * @return r
     */
    public Result<String> updateSource(GridParam gridParam, boolean flag) {
        SourceAttrParam sourceAttrParam = new SourceAttrParam();
        sourceAttrParam.setDeviceId(gridParam.getRid());
        sourceAttrParam.setSourceId(SUFFIX);
        HashMap<String, String> sa = Maps.newHashMap();
        sa.put("Enabled", flag ? "true" : "false");
        if(flag&&StringUtils.isNotEmpty(gridParam.getAudioBitrate())){
            String bitrate =gridParam.getAudioBitrate().toUpperCase();
            if (bitrate.endsWith("K")) {
                bitrate = bitrate.substring(0, bitrate.length() - 1);
            }
            sa.put("AB",bitrate);
        }
        sourceAttrParam.setSourceAttr(sa);
        log.error("update source param:{}",sourceAttrParam);
        Result<String> stringResult = gcsFeignInterface.updateSourceAttr(sourceAttrParam);
        if (!stringResult.getErrorCode().equals(ResultUtil.SUCCESS)) {
            throw new ShareException("82000501","update gcs grid source error :" + stringResult.getResult());
        }
        log.error("update source result:{}",stringResult);
        return stringResult;

    }



    @Override
    public Result<Object> getGridStatus(String gridId) throws Exception{
        Device currentDevice = deviceFeignService.getCurrentDevice(gridId);
        return ResultUtil.success(currentDevice);
    }

    @Override
    public Result<Object> addOrUpdateGrid(GridDto gridDto) {
        if(gridDto.getRid()!=null&&gridDto.getRid().equals(gridDto.getPairPid())){
            return ResultUtil.paramError("pair error");
        }
        if(StringUtils.isEmpty(gridDto.getResourceId())){
            gridDto.setResourceId(SUFFIX);
        }
        return mediaFeignService.addOrUpdateGridOutput(gridDto);
    }

    @Override
    public Result getGridList(String rids, String condition) {
        List<GridDto> gridDtoList = mediaFeignService.getGrids(rids);
        if(CollectionUtils.isEmpty(gridDtoList)){
            return ResultUtil.success(new ArrayList<>());
        }
        gridDtoList=gridDtoList.stream().filter(e->StringUtils.isNotEmpty(e.getRid())).filter(e->!e.getRid().equals(e.getPairPid())).collect(Collectors.toList());
        List<GridVo> gridVos= Beanutils.copyList(gridDtoList,GridVo.class);
        List<String> pairIds = gridVos.stream().map(GridDto::getPairPid).filter(Objects::nonNull).collect(Collectors.toList());
        if(!CollectionUtils.isEmpty(pairIds)){
            ListDeviceParam param = new ListDeviceParam();
            param.setPeerIds(String.join(",",pairIds));
            List<Device> pairDevices = deviceFeignService.listCurrentDevice(param);
            Map<String, Device> deviceMap = pairDevices.stream().collect(Collectors.toMap(Device::getPeerId, e -> e, (v1, v2) -> v2));
            for (GridVo grid : gridVos) {
                if(!StringUtils.isEmpty(grid.getPairPid())&&deviceMap.get(grid.getPairPid())!=null&&(deviceMap.get(grid.getPairPid()).getLivePeerId()+"").startsWith(grid.getRid())){
                    grid.setCurrentGrid(true);
                    grid.setSort(2);
                }
                if(deviceMap.get(grid.getPairPid())!=null){
                    grid.setPairDeviceName(deviceMap.get(grid.getPairPid()).getName());
                }
                if(!StringUtils.isEmpty(grid.getPairPid())&&deviceMap.get(grid.getPairPid())!=null){
                    grid.setStatus(Integer.valueOf(deviceMap.get(grid.getPairPid()).getStatus()));
                }
                if(!grid.isCurrentGrid()&&grid.getStatus()==1){
                    grid.setSort(1);
                }
            }
            gridVos.sort(Comparator.comparing(GridVo::getSort).reversed());
        }
        if(StringUtils.isNotEmpty(condition)){
            gridVos=gridVos.stream().filter(e->StringUtils.containsIgnoreCase(e.getPairDeviceName(),condition)).collect(Collectors.toList());
        }
        return ResultUtil.success(gridVos);
    }

    @Override
    public Result delete(String rids,String targetId,String pairId) {

        List<GridDto> grids =getGrids(rids,targetId,pairId);
        for (GridDto grid : grids) {
            if(grid.getPairPid()!=null){
                gcsFeignInterface.removePairForGM(grid.getRid(),grid.getPairPid(),SUFFIX);
            }
        }
        Set<String> collect = grids.stream().map(e -> e.getRid()).collect(Collectors.toSet());
        rids=String.join(",",collect);
        if(StringUtils.isEmpty(pairId)){
            for (String deviceId : collect) {
                gcsFeignInterface.removeGrid(deviceId);
            }
        }
        return mediaFeignService.deleteGrid(rids,pairId);
    }

    /**
     *
     * @param rids rids
     * @param targetId targetId
     * @param pairId pairId
     * @return list
     */
    private List<GridDto> getGrids(String rids, String targetId, String pairId) {
        if(StringUtils.isNotEmpty(rids)){
            return mediaFeignService.getGrids(rids);
        }
        GridDto gridDto = new GridDto();
        gridDto.setRid(rids);
        gridDto.setTargetId(targetId);
        gridDto.setPairPid(pairId);
        return mediaFeignService.getGrids(gridDto);
    }

    @Override
    public Result<Object> pair(GridParam gridParam) throws IOException {
        existAndAddGrid(gridParam);
        Device dbDevice = deviceFeignService.getDBDevice(gridParam.getRid());
        Result xAndSource = gcsFeignInterface.createGridSource(gridParam.getRid(), CLOUD_R_TYPE, gridParam.getPairPid(),gridParam.getResourceId() , dbDevice.getIp());
        if (!ResultUtil.SUCCESS.equals(xAndSource.getErrorCode())) {
            return ResultUtil.error(xAndSource.getErrorCode(),xAndSource.getErrorInfo(),xAndSource.getResult());
        }
        updateSource(gridParam,true);
        ReceiverActionResult receiverActionResult = commandUtil.actionCommand(gridParam.getRid(), CATEGORY_ID_FOR_REFRESH_GCS_CONFIG, OPERATE_TYPE_FOR_REFRESH_GCS_CONFIG,
                JsonUtil.toJsonStr(new RefreshGcsConfigVo().setMode(0)),gridParam.getOrigin());
        log.error("grid refresh gcs info:{}", receiverActionResult);
        if(receiverActionResult.assertSuccess()){
            return ResultUtil.success(gridParam.getRid()+gridParam.getResourceId());
        }else {
            return ResultUtil.error(receiverActionResult.getErrorCode(),receiverActionResult.getErrorInfo());
        }
    }

    @Override
    public Result getUserDevice(String userId, GridDeviceParam rid) {
        Result<List<Device>> deviceByUserId = deviceExtraFeignService.getDeviceByUserId(userId);
        if(!deviceByUserId.assertSuccess()||CollectionUtils.isEmpty(deviceByUserId.getResult())){
            return deviceByUserId;
        }
        //过滤自身主r
        List<Device> r =deviceByUserId.getResult().stream().filter(e->!e.getPeerId().equalsIgnoreCase(rid.getRid())).collect(Collectors.toList());
        List<DeviceExtVo> deviceExtVos = Beanutils.copyList(r, DeviceExtVo.class);

        //主r已配对的信息
        List<GridDto> grids = mediaFeignService.getGrids(rid.getRid());
        //补上resourceId
        grids.forEach(e->{if(e.getResourceId()==null){e.setResourceId(SUFFIX);}});
        List<String> arrays = grids.stream().map(GridDto::getPairPid).collect(Collectors.toList());

        deviceExtVos.forEach(e->{
            if(arrays.contains(e.getPeerId())){
                e.setHasCurrentPaired(true);
                e.setPostion(2);
            }else if(LocalConstant.DeviceStatus.ONLINE.equals(e.getStatus())){
                e.setPostion(1);
            }
        });
        deviceExtVos=deviceExtVos.stream().sorted(Comparator.comparing(Device::getPostion).thenComparing(Device::getStatus).reversed()).collect(Collectors.toList());
        if(StringUtils.isNotEmpty(rid.getCondition())){
            deviceExtVos=deviceExtVos.stream().filter(e->(StringUtils.containsIgnoreCase(e.getPeerId(),rid.getCondition())
                    ||StringUtils.containsIgnoreCase(e.getName(),rid.getCondition()))).collect(Collectors.toList());
        }
        int pageNum = rid.getPageNum();
        int index=(pageNum-1)* rid.getPageSize();
        int end=Math.min(index+rid.getPageSize(),deviceExtVos.size());
        List<DeviceExtVo> data=new ArrayList<>();
        if(index<=deviceExtVos.size()){
           data=deviceExtVos.subList(index, end);
        }
        PageResultDto<DeviceExtVo> pageResult=new PageResultDto<>(data,rid.getPageNum(),rid.getPageSize(),deviceExtVos.size(),0);
        pageResult.setTotal(deviceExtVos.size());
        pageResult.setPages((pageResult.getTotal()/rid.getPageSize())+(pageResult.getTotal()%rid.getPageSize()==0?0:1));
        return ResultUtil.success(pageResult);
    }

    @Override
    public Result removePair(String rid, String targetId, String pairId) {
        List<GridDto> grids = mediaFeignService.getGrids(rid);
        for (GridDto grid : grids) {
            gcsFeignInterface.removePairForGM(grid.getRid(),grid.getPairPid(),SUFFIX);
        }
        for (GridDto grid : grids) {
            grid.setStatus(0);
            mediaFeignService.addOrUpdateGridOutput(grid);
        }
        return ResultUtil.success();
    }

    /**
     * generator uuid
     * @return str
     */
    private String buildUUID(){
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     *
     * @param sourceId sourceId
     * @param liveMode live
     * @return String
     */
    public static String startStopLive(String sourceId,String liveMode){
        Map<String, String> map = new HashMap<>();
        map.put("TIdHex", sourceId);
        if(!StringUtils.isEmpty(liveMode)){
            map.put("LiveMode",liveMode);
        }
        try {
            return MyUtil.MAPPER.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            log.error("msg:{},e:{}",e.getMessage(),e);
            log.error(e.toString(), e);
        }
        return null;
    }


}
